// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UIVirtualGrid.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.UI.Forms
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Windows.Forms;

    using Org.OpenTrader.Framework.Events;

    #endregion

    /// <summary>
    /// The ui virtual grid.
    /// </summary>
    public partial class UIVirtualGrid : UIAbstractDataGridView
    {
        // refresh info
        #region Constants and Fields

        /// <summary>
        /// The refreshableColumns.
        /// </summary>
        private readonly IDictionary<int, DataGridViewColumn> refreshableColumns;

        /// <summary>
        /// The visibleRows.
        /// </summary>
        private readonly IDictionary<int, DataGridViewRow> visibleRows;

        /// <summary>
        /// The refreshable.
        /// </summary>
        private UIRefreshable refreshable;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="UIVirtualGrid"/> class.
        /// </summary>
        public UIVirtualGrid()
        {
            this.InitializeComponent();

            this.visibleRows = new Dictionary<int, DataGridViewRow>();
            this.refreshableColumns = new Dictionary<int, DataGridViewColumn>();

            // Event handler for cells
            base.CellValueNeeded += this.CellValueNeeded;
            base.RowStateChanged += this.RowStateChanged;
            base.ColumnAdded += this.ColumnAdded;

            // No selection
            this.ClearSelection();
        }

        #endregion

        #region Events

        /// <summary>
        /// The ui grid query cell info.
        /// </summary>
        [Description("Ask for the cell content using a handler specified by user")]
        [Category("UI Virtual Grid")]
        [Browsable(true)]
        public event EventHandler<VirtualGridEventArgs.QueryCellInfo> UIGridQueryCellInfo;

        /// <summary>
        /// The ui grid query row count.
        /// </summary>
        [Description("Ask for the current row count using a handler specified by user")]
        [Category("UI Virtual Grid")]
        [Browsable(true)]
        public event EventHandler<VirtualGridEventArgs.QueryRowCount> UIGridQueryRowCount;

        #endregion

        #region Properties

        /// <summary>
        /// Gets Columns.
        /// </summary>
        [Category("UI Virtual Grid")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Editor("System.Windows.Forms.Design.DataGridViewColumnCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        [MergableProperty(false)]
        public new DataGridViewColumnCollection Columns
        {
            get
            {
                return base.Columns;
            }
        }

        /// <summary>
        /// Gets or sets RefreshPeriodTotalMilliSeconds.
        /// </summary>
        [Category("UI Virtual Grid")]
        public int RefreshPeriodTotalMilliSeconds { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// The on handle created.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnHandleCreated(EventArgs e)
        {
            if (!this.DesignMode)
            {
                // Ready to refresh
                if (this.RefreshPeriodTotalMilliSeconds < 200)
                {
                    this.RefreshPeriodTotalMilliSeconds = 200;
                }

                this.refreshable =
                    new UIRefreshable(
                        new MyUIRefreshableInfo(this, new TimeSpan(0, 0, 0, 0, this.RefreshPeriodTotalMilliSeconds)));
                Singleton<UIRefreshThread>.Instance.AddRefreshable(this.refreshable);
            }

            base.OnHandleCreated(e);
        }

        /// <summary>
        /// The on handle destroyed.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnHandleDestroyed(EventArgs e)
        {
            if (!this.DesignMode)
            {
                Singleton<UIRefreshThread>.Instance.RemoveRefreshable(this.refreshable);
            }

            base.OnHandleDestroyed(e);
        }

        /// <summary>
        /// Extend the value needed event
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private new void CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
        {
            if (this.DesignMode)
            {
                return;
            }

            // We are between DataGridView and UIGridControl inheritor
            var args = new VirtualGridEventArgs.QueryCellInfo(e.RowIndex, e.ColumnIndex);
            this.UIGridQueryCellInfo(sender, args);

            try
            {
                // Set value if needed
                if (args.Value != null)
                {
                    e.Value = args.Value;
                }

                if (args.ToolTip != null)
                {
                    this[e.ColumnIndex, e.RowIndex].ToolTipText = args.ToolTip;
                }
            }
            catch
            {
            }
        }

        /// <summary>
        /// Keep the whole thing working if columns are added
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private new void ColumnAdded(object sender, DataGridViewColumnEventArgs e)
        {
            if (this.DesignMode)
            {
                return;
            }

            if ((e.Column is DataGridViewBlinkingTextBoxColumn || e.Column is DataGridViewDisableButtonColumn)
                && !this.refreshableColumns.ContainsKey(e.Column.Index))
            {
                this.refreshableColumns.Add(e.Column.Index, e.Column);
            }
        }

        /// <summary>
        /// Maintain a list of visible rows
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private new void RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
        {
            if (this.DesignMode)
            {
                return;
            }

            var state = e.Row.State;

            if ((state & DataGridViewElementStates.Displayed) == 0)
            {
                // This row is no more displayed
                lock (this.visibleRows)
                {
                    if (this.visibleRows.ContainsKey(e.Row.Index))
                    {
                        this.visibleRows.Remove(e.Row.Index);
                    }
                }
            }
            else
            {
                // This row is now displayed
                lock (this.visibleRows)
                {
                    if (!this.visibleRows.ContainsKey(e.Row.Index))
                    {
                        this.visibleRows.Add(e.Row.Index, e.Row);
                    }
                }
            }
        }

        #endregion

        /// <summary>
        /// The my ui refreshable info.
        /// </summary>
        internal class MyUIRefreshableInfo : UIRefreshableInfo
        {
            #region Constants and Fields

            /// <summary>
            /// The gridControl.
            /// </summary>
            private readonly UIVirtualGrid gridControl;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="MyUIRefreshableInfo"/> class.
            /// </summary>
            /// <param name="control">
            /// The control.
            /// </param>
            /// <param name="refreshPeriod">
            /// The refreshPeriod.
            /// </param>
            public MyUIRefreshableInfo(UIVirtualGrid control, TimeSpan refreshPeriod)
                : base(refreshPeriod)
            {
                this.gridControl = control;
            }

            #endregion

            #region Delegates

            /// <summary>
            /// The cross thread safe set row count delegate.
            /// </summary>
            /// <param name="rowCount">
            /// The rowCount.
            /// </param>
            private delegate void CrossThreadSafeSetRowCountDelegate(int rowCount);

            #endregion

            #region Public Methods

            /// <summary>
            /// The ui refresh.
            /// </summary>
            public override void UIRefresh()
            {
                // no handler 
                if (this.gridControl.UIGridQueryRowCount == null)
                {
                    goto done;
                }

                // no handler
                if (this.gridControl.UIGridQueryCellInfo == null)
                {
                    goto done;
                }

                // Query the row count
                var rowArg = new VirtualGridEventArgs.QueryRowCount();
                this.gridControl.UIGridQueryRowCount(this.gridControl, rowArg);
                this.gridControl.BeginInvoke(
                    new CrossThreadSafeSetRowCountDelegate(this.CrossThreadSafeSetRowCount), 
                    new object[] { rowArg.RowCount });

                // Special processing for blinkable cells
                lock (this.gridControl.visibleRows)
                {
                    // This are currently displayed rows
                    foreach (var row in this.gridControl.visibleRows.Keys)
                    {
                        // only process blinkable celles for a given row
                        foreach (var col in this.gridControl.refreshableColumns.Keys)
                        {
                            try
                            {
                                // Query the cell
                                var args = new VirtualGridEventArgs.QueryCellInfo(row, col);
                                args.Value = this.gridControl[col, row].Value;
                                this.gridControl.UIGridQueryCellInfo(this.gridControl, args);

                                var cell = this.gridControl[col, row] as DataGridViewBlinkingTextBoxCell;
                                if (cell != null)
                                {
                                    cell.Value = args.Value;
                                }
                            }
                            catch
                            {
                            }
                        }
                    }
                }

                done:

                // For ever refresh me every TimeSpan (else it's one shot)
                this.RefreshNeeded = true;
            }

            #endregion

            #region Methods

            /// <summary>
            /// The cross thread safe set row count.
            /// </summary>
            /// <param name="rowCount">
            /// The rowCount.
            /// </param>
            private void CrossThreadSafeSetRowCount(int rowCount)
            {
                if (rowCount < 1)
                {
                    return;
                }

                if (this.gridControl.RowCount != rowCount)
                {
                    this.gridControl.RowCount = rowCount;
                }
            }

            #endregion
        }
    }
}